#include "GLRenderingContext.h"
using namespace ScienceGL;

// initialize the global current rendering context
GLRenderingContext* GLRenderingContext::_currentRenderingContext = nullptr;

GLRenderingContext* GLRenderingContext::GetCurrentContext()
{
	return _currentRenderingContext;
}

GLRenderingContext::GLRenderingContext()
	: _hDC(nullptr)
	, _renderingContext(nullptr)
{
	GetMaxOpenGLVersion();
}

GLRenderingContext::GLRenderingContext(int majorVersion, int minorVersion)
	: _hDC(nullptr)
	, _renderingContext(nullptr)
{
	// check for the opengl version
	if (CheckOpenGLVersion(majorVersion, minorVersion) != GLError::GLError_Success)
		GetMaxOpenGLVersion();	
	else
	{
		_majorVersion = majorVersion;
		_minorVersion = minorVersion;
	}
}

GLRenderingContext::~GLRenderingContext()
{

}

GLError GLRenderingContext::CreateContext(HDC hDC, BYTE colorDepth)
{
	// Delete the rendering context if it is currently available
	// this is essential for that no two rendering contexts can be created
	// on the same window on the same time
	DeleteContext();

	// Store the device context so that we can use it again
	_hDC = hDC;

	// Choose the pixel format of the device context, this must be
	// done before the creation of the context, and it must match the pixel
	// format of the HDC
	PIXELFORMATDESCRIPTOR pfd;
	memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
	pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); // size
	pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
	pfd.nVersion = 1; // version
	pfd.iPixelType = PFD_TYPE_RGBA; // color type
	pfd.cColorBits = colorDepth; // preferred color depth
	pfd.cDepthBits = 24; // depth buffer
	pfd.iLayerType = PFD_MAIN_PLANE; // main layer

	// choose best matching pixel format, return index
	int pixelFormat = ChoosePixelFormat(_hDC, &pfd);

	// set pixel format to device context
	SetPixelFormat(_hDC, pixelFormat, &pfd);

	// Init GLEW extension library
	HGLRC tempContext = wglCreateContext(_hDC);
	wglMakeCurrent(_hDC, tempContext);
	glewExperimental = GL_TRUE;
	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		wglMakeCurrent(_hDC, nullptr);
		wglDeleteContext(tempContext);
		return GLException::ThrowException(GLError::GLError_OpenGLNotSupported, "Failed to initialize the GLEW library", this);
	}

	// Check that the version of open gl is greater than or equal 4.5
	if (WGL_ARB_create_context)
	{
		// Set the version that we want, in this case 4.5
		int attribs[] = {
			WGL_CONTEXT_MAJOR_VERSION_ARB, _majorVersion,
			WGL_CONTEXT_MINOR_VERSION_ARB, _minorVersion,
			0 }; // zero indicates the end of the array

		// Create an OpenGL 4.5 context using the new function and the attribs array
		_renderingContext = wglCreateContextAttribsARB(_hDC, nullptr, attribs);
		if (!_renderingContext)
			return GLException::ThrowException(GLError::GLError_CannotCreateRenderingContext, "Cannot create the rendering context for this window", this);
	}
	else
		return GLException::ThrowException(GLError::GLError_OpenGLNotSupported, "OpenGL Not Supported", this);

	// Delete the temp context
	wglMakeCurrent(_hDC, nullptr);
	wglDeleteContext(tempContext);

	// Set this rendering context to be the current rendering context to activate it
	ActivateContext();

	return GLError::GLError_Success;
}

GLError GLRenderingContext::DeleteContext()
{
	// Check the context is valid
	if (_renderingContext)
	{
		// Deactivate the rendering context
		DeActivateContext();

		// Delete the rendering context
		BOOL deleted = wglDeleteContext(_renderingContext);
		if (!deleted)
			return GLException::ThrowException(GLError::GLError_CannotDeleteRenderingContext, "Cannot delete the rendering context for this window", this);
		
		// Null the pointers
		_hDC = nullptr;
		_renderingContext = nullptr;
	}

	return GLError::GLError_Success;
}

GLError GLRenderingContext::ActivateContext()
{
	// Check the context is valid
	if (_renderingContext && _hDC)
	{
		// Activate the rendering context
		BOOL activated = wglMakeCurrent(_hDC, _renderingContext);
		if (!activated)
			return GLException::ThrowException(GLError::GLError_CannotActivateRenderingContext, "Cannot activate the rendering context for this window", this);
	}
	else
		return GLException::ThrowException(GLError::GLError_InvalidRenderingContext, "Invalid rendering context", this);

	// Set the global pointer of the current active rendering context to this
	_currentRenderingContext = this;

	return GLError::GLError_Success;
}

GLError GLRenderingContext::DeActivateContext()
{
	// Check the context is valid
	if (_renderingContext && _hDC)
	{
		// Activate the rendering context
		BOOL deActivated = wglMakeCurrent(_hDC, NULL);
		if (!deActivated)
			return GLException::ThrowException(GLError::GLError_CannotDeActivateRenderingContext, "Cannot de-activate the rendering context for this window", this);
	}
	else
		return GLException::ThrowException(GLError::GLError_InvalidRenderingContext, "Invalid rendering context", this);

	// Set the global pointer of the current active rendering context to null
	_currentRenderingContext = nullptr;

	return GLError::GLError_Success;
}

HDC GLRenderingContext::GetDC()
{
	return _hDC;
}

HGLRC GLRenderingContext::GetRenderingContext()
{
	return _renderingContext;
}


int GLRenderingContext::GetMajorVersion()
{
	return _majorVersion;
}

int GLRenderingContext::GetMinorVersion()
{
	return _minorVersion;
}

GLError GLRenderingContext::GetMaxOpenGLVersion()
{
	_majorVersion = 3;
	_minorVersion = 0;

	return GLError::GLError_Success;
}

GLError GLRenderingContext::CheckOpenGLVersion(int majorVersion, int minorVersion)
{
	return GLError::GLError_Success;
}